#include "SIMS.h"

// 已向前声明到SIMS.h
Admin* admin;
Table* table;

/// @brief 打印菜单
void menu() {
#ifdef ENGLISH
    printf("+=========== Student Information Management System ============+\n");
    printf(
        "|    1. Add      2. Delete      3. Find      4. Show           |\n");
    printf(
        "|    5. Modify   6. Sort        0. Exit                        |\n");
#else
    printf("+======================= 学生信息管理系统 ========================+\n");
    printf(
        "|    1. 添加      2. 删除      3. 查找      4. 显示            |\n");
    printf("|    5. 修改      6. 排序      0. 退出                        |\n");
#endif
    printf("+==============================================================+\n");
}

/// @brief 加载管理员信息，存储到admin中
/// @param fileName 管理员信息文件路径
void LoadAdmin(char* fileName) {
    FILE* fp = fopen(fileName, "r");
    CHECK_NULL(fp, "LoadAdmin: open file failed.\n", );
    admin = MALLOC(Admin, 1);
    // 读取管理员信息
    fscanf(fp, "%s %s", admin->username, admin->password);
    fclose(fp);
}

/// @brief 加载学生信息，存储到table中，包含扩容机制
/// table->size是当前学生数量，table->capacity是当前数组容量
/// @param fileName 学生信息文件路径
void LoadInfo(char* fileName) {
    FILE* fp = fopen(fileName, "r");
    // 检查文件是否打开成功，函数虽然没有返回值，但是CHECK_NULL中','仍要写
    CHECK_NULL(fp, "LoadInfo: open file failed.\n", );
    Stu stu;
    // 读取学生信息
    while (fscanf(fp, "%12s %20s %d %d %d %d %d", stu.id, stu.name, &stu.age,
                  &stu.gender, &stu.score.chinese, &stu.score.math,
                  &stu.score.english) == 7) {
        stu.score.total =
            stu.score.chinese + stu.score.math + stu.score.english;
        stu.score.average = stu.score.total * 1.0 / 3;
        // 初始化table
        if (table->size == 0) {
            table->data = MALLOC(Stu, DEFAULT_SIZE);
            table->capacity = DEFAULT_SIZE;
            CHECK_NULL(table->data, "LoadInfo: malloc failed.\n", );
        }
        // 扩容
        if (table->size >= table->capacity) {
            int new_capacity = table->capacity * 2;
            Stu* new_data = (Stu*)realloc(table->data, new_capacity);

            CHECK_NULL(new_data, "LoadInfo: realloc failed.\n", );
            table->data = new_data;
            table->capacity = new_capacity;
        }
        // 存储数据
        table->data[table->size++] = stu;
    }
    fclose(fp);
}

/// @brief 管理员登录，最多尝试3次，三次后等待120秒
/// @return 登录成功返回true，失败返回false
/// @note 登录成功后，attempts重置为0，登录失败后，attempts加1
void Login() {
    int attempts = 0;

    char username[MAX_NAME_LENGTH];
    char password[MAX_PASSWORD_LENGTH];
login:
    if (attempts >= MAX_ATTEMPT) {
#ifdef ENGLISH
        printf("too many attempts, please try again later. (%dmin)\n",
               ATTEMPT_LETTER / 60 / 1000);
#else
        printf("尝试次数过多，请%d分钟后再试\n", ATTEMPT_LETTER / 60 / 1000);
#endif
        Sleep(ATTEMPT_LETTER);
        attempts = 1;
        goto login;
    }
#ifdef ENGLISH
    printf("please input username and password, use space to separate: ");
#else
    printf("请输入用户名和密码，用空格分开: ");
#endif
    scanf("%s %s", username, password);
    if (strcmp(username, admin->username) == 0 &&
        strcmp(password, admin->password) == 0) {
#ifdef ENGLISH
        printf("login success\n");
#else
        printf("登录成功\n");
#endif
        attempts = 0;
    } else {
        attempts++;
#ifdef ENGLISH
        printf("username or password error, now you have %d chances\n",
               MAX_ATTEMPT - attempts);
#else
        printf("用户名或密码错误，还剩%d次机会\n", MAX_ATTEMPT - attempts);
#endif
        goto login;
    }
}

/// @brief 初始化table，包含初始化数组容量
/// @note 初始化table后，table->size为0，table->capacity为DEFAULT_SIZE
/*注意：并未给table->data申请内存，
需要在加载学生信息后再申请内存或当学生信息为空时第一次添加时惰性申请内存*/
void InitTable() {
    table = MALLOC(Table, 1);
    table->size = 0;
    LoadInfo(filename_stu_info);
}  // D:/Code/vsCode/SIMS/resource/stuInfo.txt

// 增删改查
/// @brief 添加学生信息，包含扩容机制
/// @return 添加成功返回true，失败返回false
bool AddStuInfo() {
    if (table->size == 0) {
        table->data = MALLOC(Stu, DEFAULT_SIZE);
        table->capacity = DEFAULT_SIZE;
        CHECK_NULL(table->data, "addStuInfo: malloc failed.\n", false);
    }
    if (table->size >= table->capacity) {
        table->data = (Stu*)realloc(table->data, table->capacity * 2);
        CHECK_NULL(table->data, "addStuInfo: realloc failed.\n", false);
    }
#ifdef ENGLISH
    printf("please input student info, use space to separate(enter 1 is MALE, 0 is FEMALE):> ");
    printf("id(12-digit) name(less than 20 Byte) age gender chinese math english\n");
#else
    printf("请依次输入学生信息，用空格分开(1是男性，0表示女性): \n");
    printf("学号（12位数字） 姓名（不大于20字节） 年龄 性别 语文 数学 英语\n");
#endif
    while (getchar() != '\n');

    for (bool flag = true; flag;) {
        char temp;
        if (scanf("%12[0-9]%c", table->data[table->size].id, &temp) != 2 ||
            temp != ' ') {
            if (strcmp(table->data[table->size].id, "0") == 0) {
                return false;
            }

            if (temp == '\n') {
#ifdef ENGLISH
                printf("incomplete input, please enter full data (or enter 0 to exit).\n");
#else
                printf("输入不完整！请补全数据（或输入0退出）\n");
#endif
                continue;
            }
            if (strcmp(table->data[table->size].id, "0") == 0) {
                // 输入0退出
#ifdef ENGLISH
                printf("invalid id(12-digit), please input again, enter 0 to exit.\n");
#else
                printf("无效学号（12位），请重新输入，输入0退出\n");
#endif
                return false;
            }
            continue;
        }

        if (strlen(table->data[table->size].id) != 12) {
#ifdef ENGLISH
            printf("invalid id(12-digit), please input again, enter 0 to exit.\n");
#else
            printf("无效学号（12位），请重新输入，输入0退出\n");
#endif
            continue;
        }
        for (int i = 0; i < table->size; ++i) {
            if (strcmp(table->data[i].id, table->data[table->size].id) == 0) {
#ifdef ENGLISH
                printf("id already exists, please input again, enter 0 to exit.\n");
#else
                printf("学号已存在，请重新输入，输入0退出\n");
#endif
                continue;
            }
        }

        char input[256];
        if (fgets(input, sizeof(input), stdin) == NULL) {
            printf("Error reading input.\n");
            continue;
        }

        if (sscanf(input, "%20[A-Za-z] %d %d %d %d %d",
                   table->data[table->size].name, &table->data[table->size].age,
                   &table->data[table->size].gender,
                   &table->data[table->size].score.chinese,
                   &table->data[table->size].score.math,
                   &table->data[table->size].score.english) != 6) {
#ifdef ENGLISH
            printf("Invalid input, please input again, enter 0 to exit.\n");
#else
            printf("无效输入，请重新输入，输入0退出\n");
#endif
            continue;
        }

        if (table->data[table->size].score.chinese < 0 ||
            table->data[table->size].score.chinese > 100 ||
            table->data[table->size].score.math < 0 ||
            table->data[table->size].score.math > 100 ||
            table->data[table->size].score.english < 0 ||
            table->data[table->size].score.english > 100) {
#ifdef ENGLISH
            printf("invalid score, please input again, enter 0 to exit.\n");
#else
            printf("无效成绩，请重新输入，输入0退出\n");
#endif
            continue;
        }
        if (table->data[table->size].age < 0 ||
            table->data[table->size].age > 100) {
#ifdef ENGLISH
            printf("invalid age, please input again, enter 0 to exit.\n");
#else
            printf("无效年龄，请重新输入，输入0退出\n");
#endif
            continue;
        }
        if (table->data[table->size].gender != MALE &&
            table->data[table->size].gender != FEMALE) {
#ifdef ENGLISH
            printf("invalid gender, please input again, enter 0 to exit.\n");
#else
            printf("无效性别，请重新输入，输入0退出\n");
#endif
            continue;
        }
        flag = false;
    }

    table->data[table->size].score.total =
        table->data[table->size].score.chinese +
        table->data[table->size].score.math +
        table->data[table->size].score.english;
    table->data[table->size].score.average =
        table->data[table->size].score.total * 1.0 / 3;
#ifdef ENGLISH
    printf("add student info success\n");
#else
    printf("添加学生信息成功\n");
#endif
    for (int i = 0; i < table->size; ++i) {
        printf("| %-12s | %-20s | %-2d  |   %c    |   %3d   | %3d  |   %3d   | %4d  | %7.2f |\n",
            table->data[i].id, table->data[i].name, table->data[i].age,
            (table->data[i].gender == MALE) ? 'M' : 'F',
            table->data[i].score.chinese, table->data[i].score.math,
            table->data[i].score.english, table->data[i].score.total,
            table->data[i].score.average);
    }
    table->size++;
    HeapSort(table->data, table->size, CompareById);
    return true;
}

/// @brief 根据学号删除学生信息
/// @return 删除成功返回true，失败返回false
bool DeleteStuInfo() {
    if (table->size == 0) {
#ifdef ENGLISH
        printf("no student info\n");
#else
        printf("没有学生信息\n");
#endif
        return false;
    }
    char id[MAX_ID_LENGTH];
#ifdef ENGLISH
    printf("please input student id: ");
#else
    printf("请输入学生学号: ");
#endif
    scanf("%s", id);
    for (int i = 0; i < table->size; ++i) {
        if (strcmp(id, table->data[i].id) == 0) {
            for (int j = i; j < table->size - 1; ++j) {
                table->data[j] = table->data[j + 1];
            }
        }
    }
    table->size--;
#ifdef ENGLISH
    printf("delete student info success\n");
#else
    printf("删除学生信息成功\n");
#endif
    return true;
}

/// @brief 根据学号查找学生信息
/// @return 查找成功返回true，失败返回false
bool FindStuInfo() {
    if (table->size == 0) {
#ifdef ENGLISH
        printf("no student info\n");
#else
        printf("没有学生信息\n");
#endif
        return false;
    }
    char id[MAX_ID_LENGTH];
#ifdef ENGLISH
    printf("please input student id: ");
#else
    printf("请输入学生学号: ");
#endif
    scanf("%s", id);
    for (int i = 0; i < table->size; ++i) {
        if (strcmp(id, table->data[i].id) == 0) {
            printf("+--------------+----------------------+-----+--------+---------+------+---------+-------+---------+\n");
            printf("|      ID      |        Name          | Age | Gender | Chinese | Math | English | Total | Average |\n");
            printf("+--------------+----------------------+-----+--------+---------+------+---------+-------+---------+\n");
            printf("| %-12s | %-20s | %-2d  |   %c    |   %3d   | %3d  |   %3d   | %4d  | %7.2f |\n",
                table->data[i].id, table->data[i].name, table->data[i].age,
                (table->data[i].gender == 1) ? 'M' : 'F',
                table->data[i].score.chinese, table->data[i].score.math,
                table->data[i].score.english, table->data[i].score.total,
                table->data[i].score.average);
            printf("+--------------+----------------------+-----+--------+---------+------+---------+-------+---------+\n");
            return true;
        }
    }
#ifdef ENGLISH
    printf("no such a student\n");
#else
    printf("没有该学生\n");
#endif
    return false;
}

/// @brief 显示学生信息，默认按照学号升序排序
void ShowStuInfo() {
    if (table->size == 0) {
#ifdef ENGLISH
        printf("no student info\n");
#else
        printf("没有学生信息\n");
#endif
        return;
    }

    printf("+--------------+----------------------+-----+--------+---------+------+---------+-------+---------+\n");
    printf("|      ID      |        Name          | Age | Gender | Chinese | Math | English | Total | Average |\n");
    printf("+--------------+----------------------+-----+--------+---------+------+---------+-------+---------+\n");

    for (int i = 0; i < table->size; ++i) {
        printf("| %-12s | %-20s | %-2d  |   %c    |   %3d   | %3d  |   %3d   | %4d  | %7.2f |\n",
            table->data[i].id, table->data[i].name, table->data[i].age,
            (table->data[i].gender == 1) ? 'M' : 'F',
            table->data[i].score.chinese, table->data[i].score.math,
            table->data[i].score.english, table->data[i].score.total,
            table->data[i].score.average);
    }

    printf("+--------------+----------------------+-----+--------+---------+------+---------+-------+---------+\n");
}

/// @brief 根据id修改学生信息，可修改的信息有：年龄、成绩
/// @return 修改成功返回true，失败返回false
bool ModifyStuInfo() {
    if (table->size == 0) {
#ifdef ENGLISH
        printf("no student info\n");
#else
        printf("没有学生信息\n");
#endif
        return false;
    }
    char id[MAX_ID_LENGTH];
#ifdef ENGLISH
    printf("please input student id: ");
#else
    printf("请输入学生学号: ");
#endif
    scanf("%s", id);
    for (int i = 0; i < table->size; ++i) {
        if (strcmp(id, table->data[i].id) == 0) {
#ifdef ENGLISH
            printf("please input student info, use space to separate\n");
            printf("%s %s:> age chinese math english\n", table->data[i].id, table->data[i].name);
#else
            printf("请依次输入学生信息，用空格分开\n");
            printf("%s %s:> 年龄 语文 数学 英语\n", table->data[i].id, table->data[i].name);
#endif
            bool flag = true;
            while (flag) {
                if (scanf("%d %d %d %d", &table->data[i].age,
                          &table->data[i].score.chinese,
                          &table->data[i].score.math,
                          &table->data[i].score.english) == 4) {
                    if (table->data[i].age < 0 || table->data[i].age > 100) {
#ifdef ENGLISH
                        printf("invalid age, please input again\n");
#else
                        printf("无效年龄，请重新输入\n");
#endif
                        continue;
                    }
                    if (table->data[i].score.chinese < 0 ||
                        table->data[i].score.chinese > 100 ||
                        table->data[i].score.math < 0 ||
                        table->data[i].score.math > 100 ||
                        table->data[i].score.english < 0 ||
                        table->data[i].score.english > 100) {
#ifdef ENGLISH
                        printf("invalid score, please input again\n");
#else
                        printf("无效成绩，请重新输入\n");
#endif
                        continue;
                    }
                    flag = false;
                }
            };
            table->data[i].score.total = table->data[i].score.chinese +
                                         table->data[i].score.math +
                                         table->data[i].score.english;
            table->data[i].score.average = table->data[i].score.total * 1.0 / 3;
        }
    }
}

// 比较函数
/// @brief 比较函数，用于排序
/// @param a 学生信息指针
/// @param b 学生信息指针
/// @return 比较结果，大于0表示a大于b，小于0表示a小于b，等于0表示a等于b
/// @note 比较优先级规则：
/*1. 总分 2. 数学 3. 语文 4. 英语 5. 学号*/
int CompareByScore(Stu* a, Stu* b) {
    return a->score.total == b->score.total
               ? (a->score.math == b->score.math
                      ? (a->score.chinese == b->score.chinese
                             ? (a->score.english == a->score.english
                                    ? strcmp(a->id, b->id)
                                    : a->score.english - a->score.english)
                             : a->score.chinese - b->score.chinese)
                      : a->score.math - b->score.math)
               // 反转逻辑最高分在前
               : b->score.total - a->score.total;
}

/// @note 比较优先级规则：
/*1. 年龄 2. 学号*/
int CompareByAge(Stu* a, Stu* b) {
    return a->age == b->age ? strcmp(a->id, b->id) : a->age - b->age;
}

/// @note 比较优先级规则：
/*1. 姓名 2. 学号*/
int CompareByName(Stu* a, Stu* b) { return strcmp(a->name, b->name); }

/// @note 比较优先级规则：
/*1. 学号*/
int CompareById(Stu* a, Stu* b) { return strcmp(a->id, b->id); }

/// @brief
/// @param data 要排序的学生信息数组
/// @param n 数组长度
/// @param root 父节点
/// @param end 数组尾索引
/// @param cmp 比较函数指针
/// @note 向下调整函数，用于堆排序
void sift_down(Stu* data, int n, int root, int end, int (*cmp)(Stu*, Stu*)) {
    int child = root * 2 + 1;
    while (child < end) {
        if (child + 1 < end && cmp(&data[child + 1], &data[child]) > 0) {
            ++child;
        }
        if (cmp(&data[child], &data[root]) > 0) {
            SWAP_T(Stu, data[child], data[root]);
            root = child;
            child = root * 2 + 1;
        } else {
            break;
        }
    }
}
/// @brief 从最后一个节点的父节点开始向前进行向下调整，直到根节点
void heapify(Stu* data, int n, int (*cmp)(Stu*, Stu*)) {
    for (int i = (n - 1 - 1) / 2; i >= 0; --i) sift_down(data, n, i, n, cmp);
}

/// @brief 堆排序的封装
void HeapSort(Stu* data, int n, int (*cmp)(Stu*, Stu*)) {
    // 创建堆
    heapify(data, n, cmp);
    // 记录最后一个元素的索引（直接使用n - 1也可）
    int end = n - 1;
    // 从后向前进行向下调整
    while (end > 0) {
        // 将最后一个元素与堆顶（最大元素）交换，之后最后一个元素就是最大元素
        SWAP_T(Stu, data[0], data[end]);
        // 进行向下调整
        sift_down(data, n, 0, end--, cmp);
        // 调整完毕end自减，在下一次处理时需处理数据个数减一，直到减为0结束循环
    }
    // 循环结束即为有序数组
}

/// @brief 排序的交互函数，根据用户输入选择排序方式
/// @return 排序成功返回true，失败返回false
/// @note 排序方式有：分数优先、年龄优先、姓名优先、学号优先
bool SortStuInfo() {
    if (table->size == 0) {
#ifdef ENGLISH
        printf("no student info\n");
#else
        printf("没有学生信息\n");
#endif
        return false;
    }
    int choice = 0;
    // 所有的比较函数指针数组，用于选择排序方式
    int (*cmp_rules[])(Stu*, Stu*) = {CompareByScore, CompareByAge,
                                      CompareByName, CompareById};
    const char* rules[] = {"total", "age", "name", "id"};
#ifdef ENGLISH
    printf("please input your choice: ");
    printf("1. sort by total score\n");
    printf("2. sort by age\n");
    printf("3. sort by name\n");
    printf("4. sort by id\n");
    printf("0. cancel sort\n");
#else
    printf("请输入你的选择: ");
    printf("1. 优先按总分排序\n");
    printf("2. 优先按年龄排序\n");
    printf("3. 优先按姓名排序\n");
    printf("4. 优先按学号排序\n");
    printf("0. 取消排序\n");
#endif
    scanf("%d", &choice);
    if (choice >= 1 && choice <= 4) {
        HeapSort(table->data, table->size, cmp_rules[choice - 1]);
        ShowStuInfo();
    } else {
#ifdef ENGLISH
        printf("illegal input, default sorted by id\n");
#else
        printf("非法输入，默认按学号排序\n");
#endif
    }
}
/// @brief 保存学生信息到文件中
/// @return 保存成功返回true，失败返回false
/// @note 保存学生信息到文件中，文件名为filename_stu_info
bool SaveInfo() {
    FILE* fp = fopen(filename_stu_info, "w");
    CHECK_NULL(fp, "SaveInfo: open file failed.\n", false);
    for (int i = 0; i < table->size; ++i) {
        fprintf(fp, "%s %s %d %d %d %d %d\n", table->data[i].id,
                table->data[i].name, table->data[i].age, table->data[i].gender,
                table->data[i].score.chinese, table->data[i].score.math,
                table->data[i].score.english);
    }
    fclose(fp);
}

/// @brief 释放资源
/// @note 释放资源，包括管理员信息、学生信息、table的内存空间
void FreeAll() {
    free(admin);
    free(table->data);
    free(table);
    admin = NULL;
    table = NULL;
}